﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace NES
{
    internal class M6502
    {
        // FIXME: hago los miembros provisoriamente publicos para poder trabajar mejor con el debugger
        public Commons.Processor P;
        public Commons.FncReadMemory Read;
        public Commons.FncWriteMemory Write;
        public int ClockCycles;
        private enum Flag { N = 0x80, V = 0x40, P = 0x20, B = 0x10, D = 0x08, I = 0x04, Z = 0x02, C = 0x01 }
        private enum AM { ABS, ABX, ABX_NC, ABY, ABY_NC, ACC, IMM, IMP, IND, INX, INY, INY_NC, REL, ZEP, ZPX, ZPY }

        public M6502()
        {
            P = new Commons.Processor();
            P.PC.W = 0;
            P.A = 0;
            P.X = 0;
            P.Y = 0;
            P.SP = 0xFD;
            P.F = 0x24;
            ClockCycles = 0;
        }

        public void Reset()
        {
            P = new Commons.Processor();
            P.PC.W = 0;
            P.A = 0;
            P.X = 0;
            P.Y = 0;
            P.SP = 0xFD;
            P.F = 0x24;
            ClockCycles = 0;
            P.PC.H = Read(0xFFFD);
            P.PC.L = Read(0xFFFC);
        }

        public byte Fetch()
        {
            return Read(P.PC.W++);
        }

        public int Execute(byte Opcode)
        {
            int cycles = 0;

            switch (Opcode)
            {
                case 0x01: ORA(AM.INX); break;
                case 0x05: ORA(AM.ZEP); break;
                case 0x06: ASL(AM.ZEP); break;
                case 0x08: PushToStack((byte)(P.F | 0x30)); break;
                case 0x09: ORA(AM.IMM); break;
                case 0x0A: ASL(AM.ACC); break;
                case 0x0E: ASL(AM.ABS); break;
                case 0x0D: ORA(AM.ABS); break;
                case 0x10: if (!TestFlag((byte)Flag.N)) P.PC.W = SolveAddress(AM.REL); else P.PC.W++; break;
                case 0x11: ORA(AM.INY); break;
                case 0x15: ORA(AM.ZPX); break;
                case 0x16: ASL(AM.ZPX); break;
                case 0x18: ClearFlags((byte)Flag.C); break;
                case 0x19: ORA(AM.ABY); break;
                case 0x1E: ASL(AM.ABX_NC); break;
                case 0x1D: ORA(AM.ABX); break;
                case 0x20: JSR(AM.ABS); break;
                case 0x21: AND(AM.INX); break;
                case 0x24: BIT(AM.ZEP); break;
                case 0x25: AND(AM.ZEP); break;
                case 0x26: ROL(AM.ZEP); break;
                case 0x28: P.F = PopFromStack(); ClearFlags((byte)Flag.B); SetFlags((byte)Flag.P); break;
                case 0x29: AND(AM.IMM); break;
                case 0x2A: ROL(AM.ACC); break;
                case 0x2C: BIT(AM.ABS); break;
                case 0x2D: AND(AM.ABS); break;
                case 0x2E: ROL(AM.ABS); break;
                case 0x30: if (TestFlag((byte)Flag.N)) P.PC.W = SolveAddress(AM.REL); else P.PC.W++; break;
                case 0x31: AND(AM.INY); break;
                case 0x35: AND(AM.ZPX); break;
                case 0x36: ROL(AM.ZPX); break;
                case 0x38: SetFlags((byte)Flag.C); break;
                case 0x39: AND(AM.ABY); break;
                case 0x3D: AND(AM.ABX); break;
                case 0x3E: ROL(AM.ABX_NC); break;
                case 0x40: RTI(); break;
                case 0x41: EOR(AM.INX); break;
                case 0x45: EOR(AM.ZEP); break;
                case 0x46: LSR(AM.ZEP); break;
                case 0x48: PushToStack(P.A); break;
                case 0x49: EOR(AM.IMM); break;
                case 0x4A: LSR(AM.ACC); break;
                case 0x4C: P.PC.W = SolveAddress(AM.ABS); break;
                case 0x4D: EOR(AM.ABS); break;
                case 0x4E: LSR(AM.ABS); break;
                case 0x50: if (!TestFlag((byte)Flag.V)) P.PC.W = SolveAddress(AM.REL); else P.PC.W++; break;
                case 0x51: EOR(AM.INY); break;
                case 0x55: EOR(AM.ZPX); break;
                case 0x56: LSR(AM.ZPX); break;
                case 0x58: ClearFlags((byte)Flag.I); break;
                case 0x59: EOR(AM.ABY); break;
                case 0x5E: LSR(AM.ABX_NC); break;
                case 0x5D: EOR(AM.ABX); break;
                case 0x60: P.PC.L = PopFromStack(); P.PC.H = PopFromStack(); P.PC.W++; break;
                case 0x61: ADC(AM.INX); break;
                case 0x65: ADC(AM.ZEP); break;
                case 0x66: ROR(AM.ZEP); break;
                case 0x68: PLA(); break;
                case 0x69: ADC(AM.IMM); break;
                case 0x6A: ROR(AM.ACC); break;
                case 0x6C: P.PC.W = SolveAddress(AM.IND); break;
                case 0x6D: ADC(AM.ABS); break;
                case 0x6E: ROR(AM.ABS); break;
                case 0x70: if (TestFlag((byte)Flag.V)) P.PC.W = SolveAddress(AM.REL); else P.PC.W++; break;
                case 0x71: ADC(AM.INY); break;
                case 0x75: ADC(AM.ZPX); break;
                case 0x76: ROR(AM.ZPX); break;
                case 0x78: SetFlags((byte)Flag.I); break;
                case 0x79: ADC(AM.ABY); break;
                case 0x7D: ADC(AM.ABX); break;
                case 0x7E: ROR(AM.ABX_NC); break;
                case 0x81: STA(AM.INX); break;
                case 0x84: STY(AM.ZEP); break;
                case 0x85: STA(AM.ZEP); break;
                case 0x86: STX(AM.ZEP); break;
                case 0x88: DEY(); break;
                case 0x8A: TXA(); break;
                case 0x8C: STY(AM.ABS); break;
                case 0x8D: STA(AM.ABS); break;
                case 0x8E: STX(AM.ABS); break;
                case 0x90: if (!TestFlag((byte)Flag.C)) P.PC.W = SolveAddress(AM.REL); else P.PC.W++; break;
                case 0x91: STA(AM.INY); break;
                case 0x94: STY(AM.ZPX); break;
                case 0x95: STA(AM.ZPX); break;
                case 0x96: STX(AM.ZPY); break;
                case 0x98: TYA(); break;
                case 0x99: STA(AM.ABY_NC); break;
                case 0x9A: P.SP = P.X; break;
                case 0x9D: STA(AM.ABX_NC); break;
                case 0xA0: LDY(AM.IMM); break;
                case 0xA1: LDA(AM.INX); break;
                case 0xA2: LDX(AM.IMM); break;
                case 0xA4: LDY(AM.ZEP); break;
                case 0xA5: LDA(AM.ZEP); break;
                case 0xA6: LDX(AM.ZEP); break;
                case 0xA8: TAY(); break;
                case 0xA9: LDA(AM.IMM); break;
                case 0xAA: TAX(); break;
                case 0xAC: LDY(AM.ABS); break;
                case 0xAD: LDA(AM.ABS); break;
                case 0xAE: LDX(AM.ABS); break;
                case 0xB0: if (TestFlag((byte)Flag.C)) P.PC.W = SolveAddress(AM.REL); else P.PC.W++; break;
                case 0xB1: LDA(AM.INY); break;
                case 0xB4: LDY(AM.ZPX); break;
                case 0xB5: LDA(AM.ZPX); break;
                case 0xB8: ClearFlags((byte)Flag.V); break;
                case 0xB9: LDA(AM.ABY); break;
                case 0xBA: TSX(); break;
                case 0xBC: LDY(AM.ABX); break;
                case 0xBD: LDA(AM.ABX); break;
                case 0xBE: LDX(AM.ABY); break;
                case 0xB6: LDX(AM.ZPY); break;
                case 0xC0: CPY(AM.IMM); break;
                case 0xC1: CMP(AM.INX); break;
                case 0xC4: CPY(AM.ZEP); break;
                case 0xC5: CMP(AM.ZEP); break;
                case 0xC6: DEC(AM.ZEP); break;
                case 0xC8: INY(); break;
                case 0xC9: CMP(AM.IMM); break;
                case 0xCA: DEX(); break;
                case 0xCC: CPY(AM.ABS); break;
                case 0xCD: CMP(AM.ABS); break;
                case 0xCE: DEC(AM.ABS); break;
                case 0xD0: if (!TestFlag((byte)Flag.Z)) P.PC.W = SolveAddress(AM.REL); else P.PC.W++; break;
                case 0xD1: CMP(AM.INY); break;
                case 0xD5: CMP(AM.ZPX); break;
                case 0xD6: DEC(AM.ZPX); break;
                case 0xD8: ClearFlags((byte)Flag.D); break;
                case 0xD9: CMP(AM.ABY); break;
                case 0xDD: CMP(AM.ABX); break;
                case 0xDE: DEC(AM.ABX_NC); break;
                case 0xE0: CPX(AM.IMM); break;
                case 0xE1: SBC(AM.INX); break;
                case 0xE4: CPX(AM.ZEP); break;
                case 0xE5: SBC(AM.ZEP); break;
                case 0xE6: INC(AM.ZEP); break;
                case 0xE8: INX(); break;
                case 0xE9: SBC(AM.IMM); break;
                case 0xEA: break;
                case 0xEC: CPX(AM.ABS); break;
                case 0xED: SBC(AM.ABS); break;
                case 0xEE: INC(AM.ABS); break;
                case 0xF0: if (TestFlag((byte)Flag.Z)) P.PC.W = SolveAddress(AM.REL); else P.PC.W++; break;
                case 0xF1: SBC(AM.INY); break;
                case 0xF5: SBC(AM.ZPX); break;
                case 0xF6: INC(AM.ZPX); break;
                case 0xF8: SetFlags((byte)Flag.D); break;
                case 0xF9: SBC(AM.ABY); break;
                case 0xFD: SBC(AM.ABX); break;
                case 0xFE: INC(AM.ABX_NC); break;
                default: throw new ArgumentException("PC: " + string.Format("{0:X4}", P.PC.W - 1) + "  " + string.Format("{0:X2}", Opcode) + "  Invalid opcode");
            }

            cycles = Commons.OpcodeCycles[Opcode];
            if (cycles == 0) throw new ArgumentException("PC: " + string.Format("{0:X4}", P.PC.W - 1) + "  " + string.Format("{0:X2}", Opcode) + "  Cycles error");

            ClockCycles += cycles;

            return cycles;
        }

        public void SetPC(ushort Value)
        {
            P.PC.W = Value;
        }

        public M6502 GetContext()
        {
            return (M6502)this.MemberwiseClone();
        }

        private void PushToStack(byte Value)
        {
            Write((ushort)(P.SP-- | 0x0100), Value);
        }

        private byte PopFromStack()
        {
            return Read((ushort)(++P.SP | 0x0100));
        }

        private void ClearFlags(byte Value)
        {
            P.F &= (byte)(~Value);
        }

        private void SetFlags(byte Value)
        {
            P.F |= Value;
        }

        private bool TestFlag(byte Flag)
        {
            return ((P.F & Flag) == Flag);
        }

        private ushort SolveAddress(AM Mode)
        {
            ushort result = 0;

            switch (Mode)
            {
                case AM.ABS: result = AddressAbsolute(); break;
                case AM.ABX: result = AddressAbsoluteX(); break;
                case AM.ABX_NC: result = AddressAbsoluteX_NoCross(); break;
                case AM.ABY: result = AddressAbsoluteY(); break;
                case AM.ABY_NC: result = AddressAbsoluteY_NoCross(); break;
                case AM.ACC: result = P.PC.W; break;
                case AM.IMM: result = AddressImmediate(); break;
                case AM.IND: result = AddressIndirect(); break;
                case AM.INX: result = AddressingIndirectX(); break;
                case AM.INY: result = AddressingIndirectY(); break;
                case AM.INY_NC: result = AddressingIndirectY_NoCross(); break;
                case AM.REL: result = AddressRelative(); break;
                case AM.ZEP: result = AddressZeroPage(); break;
                case AM.ZPX: result = AddressZeroPageX(); break;
                case AM.ZPY: result = AddressZeroPageY(); break;
                default: throw new ArgumentException("Invalid addressing mode");
            }

            return result;
        }

        private ushort AddressAbsolute()
        {
            Commons.PairRegister temp = new Commons.PairRegister();
            temp.L = Read(P.PC.W++);
            temp.H = Read(P.PC.W++);

            return temp.W;
        }

        private ushort AddressAbsoluteX()
        {
            Commons.PairRegister temp = new Commons.PairRegister();
            temp.L = Read(P.PC.W++);
            temp.H = Read(P.PC.W++);
            byte before = temp.H;
            temp.W += P.X;
            if (before != temp.H) ClockCycles++;

            return temp.W;
        }

        private ushort AddressAbsoluteX_NoCross()
        {
            Commons.PairRegister temp = new Commons.PairRegister();
            temp.L = Read(P.PC.W++);
            temp.H = Read(P.PC.W++);
            temp.W += P.X;

            return temp.W;
        }

        private ushort AddressAbsoluteY()
        {
            Commons.PairRegister temp = new Commons.PairRegister();
            temp.L = Read(P.PC.W++);
            temp.H = Read(P.PC.W++);
            byte before = temp.H;
            temp.W += P.Y;
            if (before != temp.H) ClockCycles++;

            return temp.W;
        }

        private ushort AddressAbsoluteY_NoCross()
        {
            Commons.PairRegister temp = new Commons.PairRegister();
            temp.L = Read(P.PC.W++);
            temp.H = Read(P.PC.W++);
            temp.W += P.Y;

            return temp.W;
        }

        private ushort AddressImmediate()
        {
            return P.PC.W++;
        }

        private ushort AddressIndirect()
        {
            Commons.PairRegister temp1 = new Commons.PairRegister();
            temp1.L = Read(P.PC.W++);
            temp1.H = Read(P.PC.W);
            Commons.PairRegister temp2 = new Commons.PairRegister();
            temp2.L = Read(temp1.W);
            temp1.L++;
            temp2.H = Read(temp1.W);

            return temp2.W;
        }

        private ushort AddressingIndirectX()
        {
            Commons.PairRegister temp = new Commons.PairRegister();
            temp.L = (byte)(Read(P.PC.W) + P.X);
            temp.L = Read(temp.L);
            temp.H = (byte)(Read(P.PC.W++) + P.X + 1);
            temp.H = Read(temp.H);

            return temp.W;
        }

        private ushort AddressingIndirectY()
        {
            Commons.PairRegister temp = new Commons.PairRegister();
            temp.L = Read(P.PC.W);
            temp.L = Read(temp.L);
            temp.H = (byte)(Read(P.PC.W++) + 1);
            temp.H = Read(temp.H);
            byte before = temp.H;
            temp.W += P.Y;
            if (before != temp.H) ClockCycles++;

            return temp.W;
        }

        private ushort AddressingIndirectY_NoCross()
        {
            Commons.PairRegister temp = new Commons.PairRegister();
            temp.L = Read(P.PC.W);
            temp.L = Read(temp.L);
            temp.H = (byte)(Read(P.PC.W++) + 1);
            temp.H = Read(temp.H);
            temp.W += P.Y;

            return temp.W;
        }

        private ushort AddressRelative()
        {
            byte before = (byte)((P.PC.W + 1) >> 8);
            short disp = Read(P.PC.W++);
            if (Commons.TestBit((byte)(disp), 7)) disp -= 0x0100;
            P.PC.W = (ushort)(P.PC.W + disp);
            ClockCycles += (before != P.PC.H ? 2 : 1);

            return P.PC.W;
        }

        private ushort AddressZeroPage()
        {
            Commons.PairRegister temp = new Commons.PairRegister();
            temp.L = Read(P.PC.W++);
            temp.H = 0;

            return temp.W;
        }

        private ushort AddressZeroPageX()
        {
            Commons.PairRegister temp = new Commons.PairRegister();
            temp.L = (byte)(Read(P.PC.W++) + P.X);
            temp.H = 0;

            return temp.W;
        }

        private ushort AddressZeroPageY()
        {
            Commons.PairRegister temp = new Commons.PairRegister();
            temp.L = (byte)(Read(P.PC.W++) + P.Y);
            temp.H = 0;

            return temp.W;
        }

        private void ADC(AM Mode)
        {
            byte value = Read(SolveAddress(Mode));
            ushort result = (ushort)(P.A + value + (TestFlag((byte)Flag.C) ? 1 : 0));
            ClearFlags((byte)(Flag.N | Flag.Z | Flag.C | Flag.V));
            if (result > 0x00FF) SetFlags((byte)Flag.C);
            if (!Commons.TestBit((byte)(P.A ^ value), 7) && Commons.TestBit((byte)(P.A ^ (result & 0x00FF)), 7)) SetFlags((byte)Flag.V);
            P.A = (byte)(result & 0x00FF);
            if (Commons.TestBit(P.A, 7)) SetFlags((byte)Flag.N);
            if (P.A == 0) SetFlags((byte)Flag.Z);
        }

        private void AND(AM Mode)
        {
            P.A &= Read(SolveAddress(Mode));
            ClearFlags((byte)(Flag.N | Flag.Z));
            if (Commons.TestBit(P.A, 7)) SetFlags((byte)Flag.N);
            if (P.A == 0) SetFlags((byte)Flag.Z);
        }

        private void ASL(AM Mode)
        {
            ushort address = SolveAddress(Mode);
            byte value = (Mode != AM.ACC ? Read(address) : P.A);
            ClearFlags((byte)(Flag.N | Flag.Z | Flag.C));
            if (Commons.TestBit(value, 7)) SetFlags((byte)Flag.C);
            value <<= 1;
            value &= 0xFE;
            if (Commons.TestBit(value, 7)) SetFlags((byte)Flag.N);
            if (value == 0) SetFlags((byte)Flag.Z);
            if (Mode != AM.ACC) Write(address, value);
            else P.A = value;
        }

        private void BIT(AM Mode)
        {
            byte value = Read(SolveAddress(Mode));
            ClearFlags((byte)(Flag.N | Flag.Z | Flag.V));
            if ((P.A & value) == 0) SetFlags((byte)Flag.Z);
            if (Commons.TestBit(value, 7)) SetFlags((byte)Flag.N);
            if (Commons.TestBit(value, 6)) SetFlags((byte)Flag.V);
        }

        private void CMP(AM Mode)
        {
            byte value = Read(SolveAddress(Mode));
            byte result = (byte)(P.A - value);
            ClearFlags((byte)(Flag.N | Flag.Z | Flag.C));
            if (Commons.TestBit(result, 7)) SetFlags((byte)Flag.N);
            if (result == 0) SetFlags((byte)Flag.Z);
            if (P.A >= value) SetFlags((byte)Flag.C);
        }

        private void CPX(AM Mode)
        {
            byte value = Read(SolveAddress(Mode));
            byte result = (byte)(P.X - value);
            ClearFlags((byte)(Flag.N | Flag.Z | Flag.C));
            if (Commons.TestBit(result, 7)) SetFlags((byte)Flag.N);
            if (result == 0) SetFlags((byte)Flag.Z);
            if (P.X >= value) SetFlags((byte)Flag.C);
        }

        private void CPY(AM Mode)
        {
            byte value = Read(SolveAddress(Mode));
            byte result = (byte)(P.Y - value);
            ClearFlags((byte)(Flag.N | Flag.Z | Flag.C));
            if (Commons.TestBit(result, 7)) SetFlags((byte)Flag.N);
            if (result == 0) SetFlags((byte)Flag.Z);
            if (P.Y >= value) SetFlags((byte)Flag.C);
        }

        private void DEC(AM Mode)
        {
            ushort address = SolveAddress(Mode);
            byte value = Read(address);
            ClearFlags((byte)(Flag.N | Flag.Z));
            value--;
            if (Commons.TestBit(value, 7)) SetFlags((byte)Flag.N);
            if (value == 0) SetFlags((byte)Flag.Z);
            Write(address, value);
        }

        private void DEX()
        {
            P.X--;
            ClearFlags((byte)(Flag.N | Flag.Z));
            if (Commons.TestBit(P.X, 7)) SetFlags((byte)Flag.N);
            if (P.X == 0) SetFlags((byte)Flag.Z);
        }

        private void DEY()
        {
            P.Y--;
            ClearFlags((byte)(Flag.N | Flag.Z));
            if (Commons.TestBit(P.Y, 7)) SetFlags((byte)Flag.N);
            if (P.Y == 0) SetFlags((byte)Flag.Z);
        }

        private void EOR(AM Mode)
        {
            P.A ^= Read(SolveAddress(Mode));
            ClearFlags((byte)(Flag.N | Flag.Z));
            if (Commons.TestBit(P.A, 7)) SetFlags((byte)Flag.N);
            if (P.A == 0) SetFlags((byte)Flag.Z);
        }

        private void INC(AM Mode)
        {
            ushort address = SolveAddress(Mode);
            byte value = Read(address);
            ClearFlags((byte)(Flag.N | Flag.Z));
            value++;
            if (Commons.TestBit(value, 7)) SetFlags((byte)Flag.N);
            if (value == 0) SetFlags((byte)Flag.Z);
            Write(address, value);
        }

        private void INX()
        {
            P.X++;
            ClearFlags((byte)(Flag.N | Flag.Z));
            if (Commons.TestBit(P.X, 7)) SetFlags((byte)Flag.N);
            if (P.X == 0) SetFlags((byte)Flag.Z);
        }

        private void INY()
        {
            P.Y++;
            ClearFlags((byte)(Flag.N | Flag.Z));
            if (Commons.TestBit(P.Y, 7)) SetFlags((byte)Flag.N);
            if (P.Y == 0) SetFlags((byte)Flag.Z);
        }

        private void JSR(AM Mode)
        {
            Commons.PairRegister temp = P.PC;
            temp.W++;
            PushToStack(temp.H);
            PushToStack(temp.L);
            P.PC.W = SolveAddress(Mode);
        }

        private void LDA(AM Mode)
        {
            P.A = Read(SolveAddress(Mode));
            ClearFlags((byte)(Flag.Z | Flag.N));
            if (P.A == 0) SetFlags((byte)Flag.Z);
            if (Commons.TestBit(P.A, 7)) SetFlags((byte)(Flag.N));
        }

        private void LDX(AM Mode)
        {
            P.X = Read(SolveAddress(Mode));
            ClearFlags((byte)(Flag.Z | Flag.N));
            if (P.X == 0) SetFlags((byte)Flag.Z);
            if (Commons.TestBit(P.X, 7)) SetFlags((byte)(Flag.N));
        }

        private void LDY(AM Mode)
        {
            P.Y = Read(SolveAddress(Mode));
            ClearFlags((byte)(Flag.Z | Flag.N));
            if (P.Y == 0) SetFlags((byte)Flag.Z);
            if (Commons.TestBit(P.Y, 7)) SetFlags((byte)(Flag.N));
        }

        private void LSR(AM Mode)
        {
            ushort address = SolveAddress(Mode);
            byte value = (Mode != AM.ACC ? Read(address) : P.A);
            ClearFlags((byte)(Flag.N | Flag.Z | Flag.C));
            if (Commons.TestBit(value, 0)) SetFlags((byte)Flag.C);
            value >>= 1;
            value &= 0x7F;
            if (value == 0) SetFlags((byte)Flag.Z);
            if (Mode != AM.ACC) Write(address, value);
            else P.A = value;
        }

        private void ORA(AM Mode)
        {
            P.A |= Read(SolveAddress(Mode));
            ClearFlags((byte)(Flag.N | Flag.Z));
            if (Commons.TestBit(P.A, 7)) SetFlags((byte)Flag.N);
            if (P.A == 0) SetFlags((byte)Flag.Z);
        }

        private void PLA()
        {
            P.A = PopFromStack();
            ClearFlags((byte)(Flag.N | Flag.Z));
            if (Commons.TestBit(P.A, 7)) SetFlags((byte)Flag.N);
            if (P.A == 0) SetFlags((byte)Flag.Z);
        }

        private void ROL(AM Mode)
        {
            ushort address = SolveAddress(Mode);
            byte value = (Mode != AM.ACC ? Read(address) : P.A);
            bool old_carry_set = TestFlag((byte)Flag.C);
            ClearFlags((byte)(Flag.N | Flag.Z | Flag.C));
            if (Commons.TestBit(value, 7)) SetFlags((byte)Flag.C);
            value <<= 1;
            value &= 0xFE;
            if (old_carry_set) value |= 0x01;
            if (Commons.TestBit(value, 7)) SetFlags((byte)Flag.N);
            if (value == 0) SetFlags((byte)Flag.Z);
            if (Mode != AM.ACC) Write(address, value);
            else P.A = value;
        }

        private void ROR(AM Mode)
        {
            ushort address = SolveAddress(Mode);
            byte value = (Mode != AM.ACC ? Read(address) : P.A);
            bool old_carry_set = TestFlag((byte)Flag.C);
            ClearFlags((byte)(Flag.N | Flag.Z | Flag.C));
            if (Commons.TestBit(value, 0)) SetFlags((byte)Flag.C);
            value >>= 1;
            value &= 0x7F;
            if (old_carry_set)
            {
                value |= 0x80;
                SetFlags((byte)Flag.N);
            }
            if (value == 0) SetFlags((byte)Flag.Z);
            if (Mode != AM.ACC) Write(address, value);
            else P.A = value;
        }

        private void RTI()
        {
            P.F = PopFromStack();
            P.PC.L = PopFromStack();
            P.PC.H = PopFromStack();
            ClearFlags((byte)Flag.B);
            SetFlags((byte)Flag.P);
        }

        private void SBC(AM Mode)
        {
            byte value = Read(SolveAddress(Mode));
            ushort result = (ushort)(P.A - value - (!TestFlag((byte)Flag.C) ? 1 : 0));
            ClearFlags((byte)(Flag.N | Flag.Z | Flag.C | Flag.V));
            if (result < 0x0100) SetFlags((byte)Flag.C);
            if (Commons.TestBit((byte)(P.A ^ (result & 0x00FF)), 7) && Commons.TestBit((byte)(P.A ^ value), 7)) SetFlags((byte)Flag.V);
            P.A = (byte)(result & 0x00FF);
            if (Commons.TestBit(P.A, 7)) SetFlags((byte)Flag.N);
            if (P.A == 0) SetFlags((byte)Flag.Z);
        }

        private void STA(AM Mode)
        {
            Write(SolveAddress(Mode), P.A);
        }

        private void STX(AM Mode)
        {
            Write(SolveAddress(Mode), P.X);
        }

        private void STY(AM Mode)
        {
            Write(SolveAddress(Mode), P.Y);
        }

        private void TAX()
        {
            P.X = P.A;
            ClearFlags((byte)(Flag.N | Flag.Z));
            if (Commons.TestBit(P.X, 7)) SetFlags((byte)Flag.N);
            if (P.X == 0) SetFlags((byte)Flag.Z);
        }

        private void TAY()
        {
            P.Y = P.A;
            ClearFlags((byte)(Flag.N | Flag.Z));
            if (Commons.TestBit(P.Y, 7)) SetFlags((byte)Flag.N);
            if (P.Y == 0) SetFlags((byte)Flag.Z);
        }

        private void TSX()
        {
            P.X = P.SP;
            ClearFlags((byte)(Flag.N | Flag.Z));
            if (Commons.TestBit(P.X, 7)) SetFlags((byte)Flag.N);
            if (P.X == 0) SetFlags((byte)Flag.Z);
        }

        private void TXA()
        {
            P.A = P.X;
            ClearFlags((byte)(Flag.N | Flag.Z));
            if (Commons.TestBit(P.A, 7)) SetFlags((byte)Flag.N);
            if (P.A == 0) SetFlags((byte)Flag.Z);
        }

        private void TYA()
        {
            P.A = P.Y;
            ClearFlags((byte)(Flag.N | Flag.Z));
            if (Commons.TestBit(P.A, 7)) SetFlags((byte)Flag.N);
            if (P.A == 0) SetFlags((byte)Flag.Z);
        }
    }
}
